#ifndef META_OBJECT_HPP
#define META_OBJECT_HPP
#include<type_traits>

#include"..\meta_invoke\meta_invoke.hpp"

namespace meta_objects {
    namespace meta_objects_details {
        struct meta_empty { static constexpr int value = 0; };
        struct meta_empty_fn { template<class T, class ...> using apply = T; };
    }

	/*A meta obj is a bind of a meta_function and an obj, each time it is invoked, it update itself to a new type,
	use ::type to get the inner obj*/
	template<class OBJ, class F/*Define how to Update an obj*/>
	struct meta_object
	{
		using type = OBJ;
		template<class ...Arg>
		using apply = meta_object<meta_invoke<F, OBJ, Arg...>, F>;

		template<class ANOTHER_OBJ>
		using meta_set = meta_object<ANOTHER_OBJ, F>;
	};

	using meta_empty_o = meta_object<meta_objects_details::meta_empty, meta_objects_details::meta_empty_fn>;

	template<class OBJ, class F, class Ret>
	struct meta_ret_object
	{
		using ret = meta_invoke<Ret, OBJ>;
		using type = OBJ;
		template<class ...Arg>
		using apply = meta_ret_object<meta_invoke<F, OBJ, Arg...>, F, Ret>;

		template<class ANOTHER_OBJ>
		using meta_set = meta_ret_object<ANOTHER_OBJ, F, Ret>;
	};

	namespace meta_timer_object_details {
		struct meta_break_signal :std::false_type {};

		template<class Fn, class DF> struct meta_break_if
		{
			template<class T> using apply = std::conditional_t<meta_invoke<Fn, T>::value, meta_break_signal, DF>;
		};
		struct meta_always_continue
		{
			template<class T> struct apply :std::false_type
			{};
		};
	}

	template<std::size_t times, class OBJ, class F, class break_f = 
		//when true, looper breaks
		meta_timer_object_details::meta_always_continue>
	struct meta_timer_object
	{
		using timer = meta_invoke<
			//if meta_invoke<break_f, OBJ>::value == true, timer = meta_break_signal
			//else timer = times > 0 ? timer - 1 : looper stop condition
			meta_timer_object_details::meta_break_if<break_f, std::integral_constant<bool, (times > 0)>>,
			OBJ
		>;

		using type = OBJ;
		template<class ...Arg>
		using apply = meta_timer_object<times - 1, meta_invoke<F, OBJ, Arg...>, F, break_f>;

		template<class ANOTHER_OBJ>
		using meta_set = meta_timer_object<times, ANOTHER_OBJ, F, break_f>;

		template<size_t reset_time>
		using reset = meta_timer_object<reset_time, OBJ, F, break_f>;
	};

	namespace meta_timer_object_details {
		template<class OBJ, size_t N, class break_f> struct To_Timer {};
		template<class obj, class F, size_t N, class break_f> struct To_Timer<meta_object<obj, F>, N, break_f>
		{
			using type = meta_timer_object<N, obj, F, break_f>;
		};

		template<class mo, class F> struct Break_If {};
		template<size_t N, class OBJ, class MO_F, class F> struct Break_If<meta_timer_object<N, OBJ, MO_F>, F>
		{
			using type = meta_timer_object<N, OBJ, MO_F, F>;
		};
	}

	template<class OBJ, size_t N, class break_f = meta_timer_object_details::meta_always_continue> 
	using to_timer = typename meta_timer_object_details::To_Timer<OBJ, N, break_f>::type;
#include"..\exp_utilities\get_type.hpp"

    //set a break condition for meta_timer_object
	template<class MTO, class BF> 
	using break_if = exp_utilities::get_type<meta_timer_object_details::Break_If<MTO, BF>>;

	namespace meta_objects_invoke_details {
		template<class From_T, class To_T>
		struct meta_transfer_object_impl {
			using type = typename To_T::template meta_set<typename From_T::type>;
		};

		//transfer timer if invoke to a meta_timer_oject
		template<size_t times, class obj, class F, class To_T, class B>
		struct meta_transfer_object_impl<meta_timer_object<times, obj, F, B>, To_T> {
			using type = typename To_T::template meta_set<typename meta_timer_object<times, obj, F, B>::timer>;
		};

		//transfer returns if invoke to a meta_ret_object
		template<class Ret, class obj, class F, class To_T>
		struct meta_transfer_object_impl<meta_ret_object<obj, F, Ret>, To_T> {
			using type = typename To_T::template meta_set<typename meta_ret_object<obj, F, Ret>::ret>;
		};
	}

    template<class From_T, class To_T>
	using meta_transfer_object = typename meta_objects_invoke_details::meta_transfer_object_impl<From_T, To_T>::type;

	namespace meta_objects_invoke_details {
		//the meta_object is itself a meta_function
		//if two meta_objects invoked, invoke the first object with type in second object
		template<class OBJ1, class OBJ2>
		struct Meta_Object_Invoke { using type = meta_invoke<OBJ1, typename OBJ2::type>; };

		//if invoke with meta_ret_object, invoke the first object with returns
		template<class OBJ1, class Obj2, class F, class Ret>
		struct Meta_Object_Invoke<OBJ1, meta_ret_object<Obj2, F, Ret>>
		{
			using type = meta_invoke<OBJ1, typename meta_ret_object<Obj2, F, Ret>::ret>;
		};
		
	}
	template<class OBJ1, class OBJ2>
	using meta_object_invoke = typename meta_objects_invoke_details::Meta_Object_Invoke<OBJ1, OBJ2>::type;

	namespace invoke_object_if_details {
		template<bool, class MO1, class MO2> struct meta_o_branch{
			using type = MO1;
		};
		template<class MO1, class MO2> struct meta_o_branch<true, MO1, MO2>{
			using type = meta_object_invoke<MO1, MO2>;
		};
	}

	template<bool con> struct invoke_object_if
	{
		template<class MO1, class MO2> using apply = typename invoke_object_if_details::meta_o_branch<con, MO1, MO2>::type;
	};
}



#endif // !META_OBJECT_HPP
